﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Linq.Expressions;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Repositories.SearchExtensions;
using ControleRepresentante.Logic.Statistics;
using NHibernate;
using NHibernate.Criterion;
using NHibernate.SqlCommand;
using Attribute = ControleRepresentante.Logic.Models.Attribute;

namespace ControleRepresentante.Logic.Repositories
{
    public interface IRelatorioVisitaRepository : IAttributeHolderRepository<RelatorioVisita, StatisticsBase>
    {
        IEnumerable<RelatorioVisita> GetModels(DateTime? de, DateTime? ate, string cliente, IEnumerable<Guid> vendedorIds, string observacao, Dictionary<Attribute, string> attributeMap, ISearchExtension[] searchExtensions, out StatisticsBase statistics, int page);
        int[] FilterSynchronizedRelatorioVisitas(int[] relatorioVisitas);
        void AddModels(RelatorioVisita[] relatorioVisitas);
    }

    public class RelatorioVisitaRepository : AttributeHolderRepository<RelatorioVisita, StatisticsBase, RelatorioVisitaError>, IRelatorioVisitaRepository
    {
        public RelatorioVisitaRepository(IRepositoryConfiguration repositoryConfiguration, ISession session)
            : base(repositoryConfiguration, session)
        {
        }

        protected override IQueryOver<RelatorioVisita, RelatorioVisita> GetModelsQueryOver(IQueryOver<RelatorioVisita, RelatorioVisita> queryOver)
        {
            return AddFiltersToQueryOver(queryOver).OrderBy(x => x.Data).Desc;
        }

        protected override IQueryOver<RelatorioVisita, RelatorioVisita> DoGetModelByIdQueryOver(IQueryOver<RelatorioVisita, RelatorioVisita> queryOver, Guid id)
        {
            return AddFiltersToQueryOver(queryOver);
        }

        protected override IEnumerable<Expression<Func<RelatorioVisita, bool>>> DoGetUniquenessExpressions(RelatorioVisita model)
        {
            yield return x => x.Id == model.Id;
        }

        protected override RelatorioVisitaError RepresentanteMissmatchError
        {
            get { return RelatorioVisitaError.RelatorioAndRepresentanteMismatch; }
        }

        protected override RelatorioVisitaError DuplicatedError { get { return RelatorioVisitaError.Duplicated; } }

        protected override void SetProperties(IEnumerable<RelatorioVisita> relatorioVisitas)
        {
            if (relatorioVisitas.Any())
            {
                Dictionary<Guid, Cliente> clientes = GetDependentModels(relatorioVisitas, x => x.Cliente);
                Dictionary<Guid, Vendedor> vendedores = GetDependentModels(relatorioVisitas, x => x.Vendedor);

                foreach (RelatorioVisita relatorioVisita in relatorioVisitas)
                {
                    relatorioVisita.ClienteNome = clientes[relatorioVisita.Cliente.Id].RazaoSocial;
                    relatorioVisita.VendedorNome = vendedores[relatorioVisita.Vendedor.Id].Nome;
                }
            }
        }

        protected override StatisticsBase GetStatistics(int modelCount)
        {
            return new StatisticsBase(modelCount);
        }

        private Dictionary<Guid, TModel> GetDependentModels<TModel>(IEnumerable<RelatorioVisita> relatorioVisitas, Func<RelatorioVisita, TModel> getDependentModel)
            where TModel : ModelBase
        {
            return Session.QueryOver<TModel>()
                .Where(x => x.Id.IsIn(relatorioVisitas.Select(y => getDependentModel(y).Id).Distinct().Cast<object>().ToArray()))
                .List()
                .ToDictionary(x => x.Id, x => x);
        }

        private IQueryOver<RelatorioVisita, RelatorioVisita> AddFiltersToQueryOver(IQueryOver<RelatorioVisita, RelatorioVisita> queryOver)
        {
            IEnumerable<Cliente> clienteFiltro = User.Filtros.OfType<Cliente>();
            if (clienteFiltro.Any())
            {
                queryOver = queryOver.And(Restrictions.In(Projections.Property<RelatorioVisita>(x => x.Cliente.Id), clienteFiltro.Select(x => x.Id).ToArray()));
            }
            IEnumerable<Vendedor> vendedorFiltro = User.Filtros.OfType<Vendedor>();
            if (vendedorFiltro.Any())
            {
                queryOver = queryOver.And(Restrictions.In(Projections.Property<RelatorioVisita>(x => x.Vendedor.Id), vendedorFiltro.Select(x => x.Id).ToArray()));
            }
            return queryOver;
        }

        public IEnumerable<RelatorioVisita> GetModels(DateTime? de, DateTime? ate, string cliente, IEnumerable<Guid> vendedorIds, string observacao, Dictionary<Attribute, string> attributeMap, ISearchExtension[] searchExtensions, out StatisticsBase statistics, int page)
        {
            const string TableAlias = "re";

            ICriteria criteria = CreateCriteria(attributeMap, TableAlias);

            if (vendedorIds != null && vendedorIds.Any())
            {
                criteria = criteria.Add(Restrictions.In(Projections.Property<RelatorioVisita>(x => x.Vendedor.Id), vendedorIds.ToArray()));
            }

            if (de.HasValue)
            {
                criteria = criteria.Add(Restrictions.Ge(Projections.Property<RelatorioVisita>(x => x.Data), de.Value));
            }

            if (ate.HasValue)
            {
                criteria = criteria.Add(Restrictions.Le(Projections.Property<RelatorioVisita>(x => x.Data), ate.Value));
            }


            if (!String.IsNullOrWhiteSpace(observacao))
            {
                criteria = criteria.Add(Restrictions.Like(Projections.Property<RelatorioVisita>(x => x.Observacao), observacao));
            }

            if (!String.IsNullOrEmpty(cliente))
            {
                string clienteLike = "%" + cliente.ToLower() + "%";
                criteria.CreateCriteria(TableAlias + ".Cliente", JoinType.InnerJoin)
                    .Add(Restrictions.Or(
                        Restrictions.InsensitiveLike(Projections.Property<Cliente>(x => x.NomeFantasia), clienteLike),
                        Restrictions.InsensitiveLike(Projections.Property<Cliente>(x => x.RazaoSocial), clienteLike)));
            }

            IEnumerable<Cliente> clienteFiltro = User.Filtros.OfType<Cliente>();
            if (clienteFiltro.Any())
            {
                criteria = criteria.Add(Restrictions.In(Projections.Property<RelatorioVisita>(x => x.Cliente.Id), clienteFiltro.Select(x => x.Id).ToArray()));
            }

            IEnumerable<Vendedor> vendedorFiltro = User.Filtros.OfType<Vendedor>();
            if (vendedorFiltro.Any())
            {
                criteria = criteria.Add(Restrictions.In(Projections.Property<RelatorioVisita>(x => x.Vendedor.Id), vendedorFiltro.Select(x => x.Id).ToArray()));
            }

            foreach (ISearchExtension searchExtension in searchExtensions)
            {
                criteria = searchExtension.Execute(criteria, TableAlias);
            }

            criteria = criteria.AddOrder(Order.Desc(Projections.Property<RelatorioVisita>(x => x.Data)));

            int modelCount;
            IEnumerable<RelatorioVisita> relatorioVisitas = FuturePaging(criteria, page, out modelCount);

            SetProperties(relatorioVisitas);
            statistics = GetStatistics(modelCount);
            
            return relatorioVisitas;
        }

        public int[] FilterSynchronizedRelatorioVisitas(int[] relatorioVisitas)
        {
            EnsureIsRepresentante();

            DetachedCriteria detachedCriteria = DetachedCriteria.For<RelatorioVisita>()
                .SetProjection(Projections.Property<RelatorioVisita>(x => x.Id))
                .Add(Restrictions.EqProperty("rvj.Id", Projections.Property<RelatorioVisita>(x => x.Id)))
                .Add(Restrictions.Eq(Projections.Property<RelatorioVisita>(x => x.Representante.Id), GetRepresentanteId()));
            IEnumerable<int> synchronizedRelatorioVisitas = Session.CreateCriteria<RelatorioVisitaJolitex>("rvj")
                .SetProjection(Projections.Property<RelatorioVisitaJolitex>(x => x.Codigo))
                .Add(Restrictions.In(Projections.Property<RelatorioVisitaJolitex>(x => x.Codigo), relatorioVisitas.ToArray()))
                .Add(Subqueries.Exists(detachedCriteria))
                .Future<int>()
                .Distinct();
            return relatorioVisitas.Except(synchronizedRelatorioVisitas).ToArray();
        }

        public void AddModels(RelatorioVisita[] relatorioVisitas)
        {
            EnsureIsRepresentante();

            using (ITransaction transaction = Session.BeginTransaction())
            {
                IEnumerable<int> relatorioVisitasToSynchronize =
                    FilterSynchronizedRelatorioVisitas(relatorioVisitas.Select(x => x.RelatorioVisitaJolitex.Codigo.Value).ToArray());
                relatorioVisitas = relatorioVisitas
                    .Where(x => relatorioVisitasToSynchronize.Contains(x.RelatorioVisitaJolitex.Codigo.Value))
                    .ToArray();

                foreach (RelatorioVisita relatorioVisita in relatorioVisitas)
                {
                    Session.Save(relatorioVisita);
                }
                transaction.Commit();
            }
        }
    }

    public enum RelatorioVisitaError
    {
        RelatorioAndRepresentanteMismatch,
        Duplicated
    }
}
